package com.atguigu.srb.core.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.srb.base.asserts.Asserts;
import com.atguigu.srb.base.consts.SrbConsts;
import com.atguigu.srb.base.response.ResponseEnum;
import com.atguigu.srb.base.util.JwtUtils;
import com.atguigu.srb.core.enums.LendStatusEnum;
import com.atguigu.srb.core.hfb.*;
import com.atguigu.srb.core.pojo.entity.*;
import com.atguigu.srb.core.mapper.LendMapper;
import com.atguigu.srb.core.pojo.entity.vo.BorrowApprovalVO;
import com.atguigu.srb.core.pojo.entity.vo.BorrowerDetailVO;
import com.atguigu.srb.core.pojo.entity.vo.LendDetailVO;
import com.atguigu.srb.core.pojo.entity.vo.LendSearchVO;
import com.atguigu.srb.core.service.*;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * <p>
 * 标的准备表 服务实现类
 * </p>
 *
 * @author Atguigu
 * @since 2023-05-26
 */
@Service
@Slf4j
public class LendServiceImpl extends ServiceImpl<LendMapper, Lend> implements LendService {
    //借款信息
    @Resource
    BorrowInfoService borrowInfoService;
    //借款人
    @Resource
    BorrowerService borrowerService;
    //会员
    @Resource
    UserInfoService userInfoService;
    //数据字典
    @Resource
    DictService dictService;
    //交易流水
    @Resource
    TransFlowService transFlowService;
    //会员账户
    @Resource
    UserAccountService userAccountService;
    //投资记录
    @Resource
    LendItemService lendItemService;
    //还款计划
    @Resource
    LendReturnService lendReturnService;
    //回款计划
    @Resource
    LendItemReturnService lendItemReturnService;
    @Override
    public void borrowApproval(BorrowApprovalVO borrowApprovalVO) {
        // springmvc的controller接口：  pojo入参 和请求体接收的对象  可能为空吗？不可能
        //对提交的参数进行空值校验：
        //1、用户账户进行校验： hfb账户绑定、用户状态
        Long borrowId = borrowApprovalVO.getBorrowId();
        //查询借款信息对象
        BorrowInfo borrowInfo = borrowInfoService.getById(borrowId);
        userInfoService.checkAccount(borrowInfo.getUserId());
        //2、借款信息状态： 必须是审核中
        Asserts.assertNotTrue(borrowInfo.getStatus()!=1 , ResponseEnum.ERROR);
        //3、判断审批结果：审批状态是不通过，更新借款申请状态为不通过即可
        Integer approvalStatus = borrowApprovalVO.getStatus();
        //无论是否通过审核，都需要更新借款信息的审核状态
        borrowInfo.setStatus(approvalStatus);
        borrowInfo.setComment(borrowApprovalVO.getComment());
        borrowInfoService.updateById(borrowInfo);
        if(approvalStatus==-1){
            return;
        }
        //4、审批通过： 新建标的
        Lend lend = new Lend();
        //设置Lend对象需要的属性值
        //借款会员id
        lend.setUserId(borrowInfo.getUserId());
        //借款申请id
        lend.setBorrowInfoId(borrowId);
        //标的编号：后端生成一个唯一的编号
        lend.setLendNo(LendNoUtils.getLendNo());
        //标的标题
        lend.setTitle(borrowApprovalVO.getTitle());
        //年化率
        lend.setLendYearRate(borrowApprovalVO.getLendYearRate());
        //平台服务费率
        lend.setServiceRate(borrowApprovalVO.getServiceRate());
        //起息日：开始计算收益的时间
        lend.setLendStartDate(new DateTime(borrowApprovalVO.getLendStartDate()).toDate());
        //标的说明：
        lend.setLendInfo(borrowApprovalVO.getLendInfo());



        //srb平台实际的收益：默认0
        lend.setRealAmount(new BigDecimal(0));
        /*
        CHECK(0, "待发布"),
        INVEST_RUN(1, "募资中"),
        PAY_RUN(2, "还款中"),
        PAY_OK(3, "已结清"),
        FINISH(4, "结标"),
        CANCEL(-1, "已撤标"),
         */
        //标的的状态：  募资中、募资成功、已撤销
        lend.setStatus(1);
        //审核时间：当前时间
        lend.setCheckTime(new Date());
        //审批管理员id：
        lend.setCheckAdminId(1L);
        //借款金额：借款信息中包含
        lend.setAmount(borrowInfo.getAmount());
        // 投资期数
        lend.setPeriod(borrowInfo.getPeriod());
        //投资期数查询到之后再计算
        //利息计算截止日志： 起息日+期数个月份
        lend.setLendEndDate(new DateTime(borrowApprovalVO.getLendStartDate())
                .plusMonths(borrowInfo.getPeriod()).toDate());
        //srb平台本标的预期收益： 利率*本金*借款时长 (月化率*借款金额*借款期数 )
        //年化率： 年化率/12 可以得到月化率   年化率*100 存入的所以 计算利息时需要除以100
        BigDecimal monthRate = borrowApprovalVO.getServiceRate().divide(new BigDecimal(1200), 8, RoundingMode.CEILING);
        BigDecimal exceptAmount = borrowInfo.getAmount().multiply(monthRate).multiply(new BigDecimal(borrowInfo.getPeriod()));
        lend.setExpectAmount(exceptAmount);
        // 还款方式
        lend.setReturnMethod(borrowInfo.getReturnMethod());
        //最低投资金额
        lend.setLowestAmount(new BigDecimal(50));
        //已投金额：默认0
        lend.setInvestAmount(new BigDecimal(0));
        //已投人数：
        lend.setInvestNum(0);
        //借款审批通过的时间： 标的生成的时间
        lend.setPublishDate(new Date());
        //放款时间、放款人id在放款时才需要设置
        this.save(lend);
    }

    @Override
    public List<Lend> listLends() {
        // Wrapper用来构建CRUD的sql语句
        // QueryWrapper: 构建查询sql  UpdateWrapper:构建更新语句
        // 如果删除时需要传入条件 上面两个都可以用
        // 如果不使用mp的Wrapper，也可以再mapper映射文件中使用自定义的sql

        //Lend.class 用来解析表名和所有的列名
        List<Lend> list = this.list(Wrappers.lambdaQuery(Lend.class)
                .orderByDesc(Lend::getCreateTime));

        list.forEach(lend->{
            //给lend设置页面需要的属性值到params中
            lend.getParams().put("lendYearRate",lend.getLendYearRate()+"%");
            lend.getParams().put("status",getLendStatusDesc(lend.getStatus()));
            lend.getParams().put("returnMethod",dictService.selectDictNameByDictCodeAndValue(SrbConsts.DICT_CODE_RETURN_METHOD,
                    lend.getReturnMethod()));
        });

        return list;
    }

    @Override
    public LendDetailVO getLendDetailVOById(String id) {
        //标的详情
        Lend lend = this.getLendById(id);
        //借款人详情: lend中的userId就是该标的的借款人会员id
        Borrower borrower = borrowerService.getOne(Wrappers.lambdaQuery(Borrower.class)
                .eq(Borrower::getUserId, lend.getUserId())
                .select(Borrower::getId));

        BorrowerDetailVO borrowerDetailVO = borrowerService.getBorrowerDetailById(borrower.getId().toString());

        LendDetailVO lendDetailVO = new LendDetailVO();
        lendDetailVO.setLend(lend);
        lendDetailVO.setBorrowerDetailVO(borrowerDetailVO);

        return lendDetailVO;
    }

    @Override
    public List<Lend> listLendsByCondition(LendSearchVO lendSearchVO) {
        LambdaQueryWrapper<Lend> wrapper = Wrappers.lambdaQuery(Lend.class);
        List<Integer> moneyUses = lendSearchVO.getMoneyUses();
        List<Integer> periods = lendSearchVO.getPeriods();
        List<Integer> yearRates = lendSearchVO.getYearRates();
        List<Integer> returnMethods = lendSearchVO.getReturnMethods();
        if(CollectionUtils.isNotEmpty(moneyUses)){

            //设置资金用途条件
            // 1- 旅游贷、2- 房易贷、3- 装修贷、4- 医疗贷、 5- 美容贷、6-其他
            // 使用标题模糊查询： 空 匹配所有
            // 如果传入的是 1，希望生成的查询条件： select * from lend where title like '旅游贷%'
            // 如果传入的是 1，5 希望生成的查询条件： select * from lend where title like '旅游贷%' or title like '美容贷%'
            wrapper.and(w->{
                moneyUses.forEach(moneyUse->{
                    if(moneyUse!=null){
                        switch (moneyUse){
                            case 1:
                                w.or().likeRight(Lend::getTitle,"旅游贷");
                                break;
                            case 2:
                                w.or().likeRight(Lend::getTitle,"买房贷");
                                break;
                            case 3:
                                w.or().likeRight(Lend::getTitle,"装修贷");
                                break;
                            case 4:
                                w.or().likeRight(Lend::getTitle,"医疗贷");
                                break;
                            case 5:
                                w.or().likeRight(Lend::getTitle,"美容贷");
                                break;
                            default:
                                w.or().likeRight(Lend::getTitle,"其他");
                                break;
                        }
                    }

                });
            });

        }

        if(CollectionUtils.isNotEmpty(periods)){
            //设置还款期数条件：
            //1- [0~1)、2- [1~3)、3- [3~6)、4- [6~12)、 5- [12及以上)
            // 如果传入的期数有 1,3,4,生成的sql
            // select * from lend where (period>=0 and period<1)  or   (period>=3 and period<6 ) or   (period>=6 and period<12)
            wrapper.and(q->{
                periods.forEach(period->{
                    // wrapper.or 当前条件和以前的条件之前使用or连接
                    q.or(w-> {  // q 代表使用()扩起来的一组条件的wrapper对象
                        switch (period){
                            case 1:
                                w.ge(Lend::getPeriod , 0);
                                w.lt(Lend::getPeriod , 1);
                                break;
                            case 2:
                                w.ge(Lend::getPeriod , 1);
                                w.lt(Lend::getPeriod , 3);
                                break;
                            case 3:
                                w.ge(Lend::getPeriod , 3);
                                w.lt(Lend::getPeriod , 6);
                                break;
                            case 4:
                                w.ge(Lend::getPeriod , 6);
                                w.lt(Lend::getPeriod , 12);
                                break;
                            case 5:
                                w.ge(Lend::getPeriod , 12);
                                break;
                        }
                    });

                });
            });

        }

        if(CollectionUtils.isNotEmpty(yearRates)){
            //设置年化率条件：
            //1- 12%以下、2- [12~14)、3- [14~16)、4- [16及以上]
            wrapper.and(q->{
                yearRates.forEach(yearRate->{
                    // wrapper.or 当前条件和以前的条件之前使用or连接
                    q.or(w-> {  // q 代表使用()扩起来的一组条件的wrapper对象
                        switch (yearRate){
                            case 1:
                                //数据库保存的年化率是100倍
                                w.lt(Lend::getLendYearRate , 12);
                                break;
                            case 2:
                                w.ge(Lend::getLendYearRate , 12);
                                w.lt(Lend::getLendYearRate , 14);
                                break;
                            case 3:
                                w.ge(Lend::getLendYearRate , 14);
                                w.lt(Lend::getLendYearRate , 16);
                                break;
                            case 4:
                                w.ge(Lend::getLendYearRate , 16);
                                break;
                        }
                    });

                });
            });

        }

        if(CollectionUtils.isNotEmpty(returnMethods)){
            //因为前端提交的还款方式的值 和 字典表还款方式值一样
            // 1 2 3 : where return_method = 1 or  return_method = 2 or  return_method = 3
            wrapper.in(Lend::getReturnMethod , returnMethods);
        }
        List<Lend> lends = this.list(wrapper);
        lends.forEach(lend->{
            //给lend设置页面需要的属性值到params中
            lend.getParams().put("status",getLendStatusDesc(lend.getStatus()));
            lend.getParams().put("returnMethod",dictService.selectDictNameByDictCodeAndValue(SrbConsts.DICT_CODE_RETURN_METHOD,
                    lend.getReturnMethod()));
        });
        return lends;
    }

    @Override
    public Lend getLendById(String id) {
        Lend lend = this.getById(id);
        lend.getParams().put("lendYearRate",lend.getLendYearRate()+"%");
        lend.getParams().put("status",getLendStatusDesc(lend.getStatus()));
        lend.getParams().put("returnMethod",dictService.selectDictNameByDictCodeAndValue(SrbConsts.DICT_CODE_RETURN_METHOD,
                lend.getReturnMethod()));
        return lend;
    }
    /*
    {
    "agentProjectCode":"LEND20230613154700190",  放款的项目编号
    "voteAmt":"3859.2001608000",  放款金额： 募资到的金额-平台服务费
    "hyFee":"0",
    "mchFee":"160.7998392000",  平台服务费
    "resultCode":"0000",
    "resultMsg":"放款成功",
    "agentBillNo":"LOAN20230617094136650"  交易唯一编号
    }
     */
    //Transactional 基于AOP实现的注解 可以对标注的方法进行事务管理
    //rollbackFor: 默认RuntimeException异常才会回滚，其他的不回滚
    //propagation: 事务传播行为 spring提供了7种事务传播行为 默认有事务直接使用 没有新创建事务执行
    // 事务隔离级别：数据库   脏读(一个事物可以读另一个事务未提交的结果 不允许脏读 ) 
    // 不可重复读(一个事务执行前后读取到的一行数据不一致 ) 幻读(一个事务执行前后读取到的表行数多了或者少了一些)
    @Transactional(rollbackFor = Exception.class )
    @Override
    public void makeLoad(String id) {
        // 放款的id可以缓存到redis中：校验id如果存在redis中表示已经处理过放款请求
        //1、参数校验
        //标的状态
        Lend lend = this.getById(id);
        //标的状态错误：不是募资中
        Asserts.assertNotTrue(lend.getStatus()!=1 , ResponseEnum.ERROR);
//        lend.getInvestAmount(): 校验投资金额是否已经达到逾期的比例
        //借款人状态是否正常
        UserInfo userInfo = userInfoService.checkAccount(lend.getUserId());
        Long userId = userInfo.getId();
        //2、同步访问hfb的放款接口
        // hfb放款接口需要的参数集合
        Map<String, Object> params = new HashMap<>();
        params.put("agentId",HfbConst.AGENT_ID);
        // 放款的项目编号：必须和投资时使用的项目编号一致
        params.put("agentProjectCode",lend.getLendNo());
        // 本次交易的流水号 唯一编号
        String loanNo = LendNoUtils.getLoanNo();
        params.put("agentBillNo", loanNo);
        //计算手续费扣除：
        // 平台服务费： 服务费率/1200 * 期数 * 募资到的总金额
        BigDecimal monthServiceRate = lend.getServiceRate().divide(new BigDecimal(1200), 8, BigDecimal.ROUND_DOWN);
        BigDecimal serviceAmount = monthServiceRate.multiply(new BigDecimal(lend.getPeriod())).multiply(lend.getInvestAmount());
        params.put("mchFee",serviceAmount);
        params.put("timestamp",System.currentTimeMillis());
        params.put("sign",RequestHelper.getSign(params));
        //同步请求hfb的放款接口得到响应结果
        JSONObject jsonObject = RequestHelper.sendRequest(params, HfbConst.MAKE_LOAD_URL);
        //3、根据放款结果 处理本地数据
        String resultCode = jsonObject.getString("resultCode");
        //业务处理失败 抛出异常
        if(!StringUtils.equals("0000",resultCode)){
            log.error("放款失败,hfb业务失败:{}",jsonObject.toJSONString());
            Asserts.assertNotTrue(!StringUtils.equals("0000",resultCode),ResponseEnum.ERROR);
        }
        //放款成功
        //放款给借款人的金额
        BigDecimal voteAmt = jsonObject.getBigDecimal("voteAmt");
        //平台手续费
        BigDecimal mchFee = jsonObject.getBigDecimal("mchFee");
        //1、更新标的的状态为 还款中
        lend.setStatus(LendStatusEnum.PAY_RUN.getStatus());
        //放款的管理员id
        lend.setPaymentAdminId(1L);
        lend.setPaymentTime(new Date());
        //平台的实际收益：
        lend.setRealAmount(lend.getRealAmount().add(mchFee));
        this.updateById(lend);
        //2、保存放款交易流水
//        UserInfo userInfo = userInfoService.getById(userId);
        TransFlow transFlow = new TransFlow();
        transFlow.setUserId(userId);
        transFlow.setUserName(userInfo.getName());
        // 交易流水号，可以使用本次交易的唯一编号设置
        transFlow.setTransNo(loanNo);
        transFlow.setTransType(5);
        transFlow.setTransTypeName("满标放款");
        transFlow.setTransAmount(lend.getInvestAmount());
        //备注信息：可以在回调中更新
//        transFlow.setMemo();
        transFlow.setStatus(1);
        transFlowService.save(transFlow);
        //3、给借款人账户入账
        UserAccount userAccount = userAccountService.getOne(Wrappers.lambdaQuery(UserAccount.class)
                .eq(UserAccount::getUserId, userId));
        userAccount.setAmount(userAccount.getAmount().add(voteAmt));
        userAccountService.updateById(userAccount);
        transFlow = new TransFlow();
        transFlow.setUserId(userId);
        transFlow.setUserName(userInfo.getName());
        transFlow.setTransNo(LendNoUtils.getNo());
        transFlow.setTransType(6);
        transFlow.setMemo(jsonObject.toJSONString());
        transFlow.setTransTypeName("放款入账");
        transFlow.setTransAmount(voteAmt);
        transFlow.setStatus(1);
        transFlowService.save(transFlow);

        //4、扣除本标的投资人账户冻结金额
        // 根据标的id先查询标的所有的投资记录
        List<LendItem> lendItems = lendItemService.list(Wrappers.lambdaQuery(LendItem.class)
                .eq(LendItem::getLendId, lend.getId()));
        lendItems.forEach(lendItem -> {
            //每个投资记录 就是一个投资人的投资，可以扣除该投资记录的投资人账户冻结金额
            userAccountService
                    .updateFreezeAmountByUserId(lendItem.getInvestUserId(),lendItem.getInvestAmount());
            TransFlow investTransFlow = new TransFlow();
            investTransFlow.setUserId(lendItem.getInvestUserId());
            investTransFlow.setUserName(lendItem.getInvestName());
            // 交易流水号，可以使用本次交易的唯一编号设置
            investTransFlow.setTransNo(LendNoUtils.getNo());
            investTransFlow.setTransType(7);
            investTransFlow.setTransTypeName("投资扣款");
            investTransFlow.setTransAmount(lendItem.getInvestAmount());
            //备注信息：可以在回调中更新
//        transFlow.setMemo();
            investTransFlow.setStatus(1);
            transFlowService.save(investTransFlow);
        });
        //电商中会学习异步编排：
        //将同步执行的代码改成 并行和串行组合的方式执行
//        CompletableFuture

        //5、借款人 投资人账户操作 每一个都保存一个交易流水
        //6、生成借款人的还款计划
        // 借款期数个还款计划，从起息日开始还款
        // 创建借款期数个还款计划对象 设置属性值 存到数据库表中
        List<LendReturn> lendReturns = lendReturnService.saveLendReturns(lend);

        //下面需要使用期数获取 对应期的 还款计划的id
        // {1:xxx , 2:xxx , 3:xxxx}
        List<LendItemReturn> lendItemReturns = lendItemReturnService.saveLendItemReturns(lend, monthServiceRate, lendItems, lendReturns);

        // 一个标的 有多个投资记录，一个投资记录又生成若干个回款计划，  每个投资记录一期还款 会分给所有投资记录该期的回款计划
        // 计算时，可能会出现精度问题

        //还款计划还款本金利息不是由自己决定，由所有回款计划决定
        lendReturnService.updateLendReturns(lendReturns, lendItemReturns);

    }

    @Override
    public boolean isMyLend(String id, String token) {
        Long userId = JwtUtils.getUserId(token);
        return  this.count(Wrappers.lambdaQuery(Lend.class)
                .eq(Lend::getId,id)
                .eq(Lend::getUserId,userId))>0;
    }


    private String getLendStatusDesc(Integer status){
        String statusDesc;

        switch(status){
            case 0:
                statusDesc = LendStatusEnum.CHECK.getConent();
                break;
            case 1:
                statusDesc = LendStatusEnum.INVEST_RUN.getConent();
                break;
            case 2:
                statusDesc = LendStatusEnum.PAY_RUN.getConent();
                break;
            case 3:
                statusDesc = LendStatusEnum.PAY_OK.getConent();
                break;
            case 4:
                statusDesc = LendStatusEnum.FINISH.getConent();
                break;
            default:
                statusDesc = LendStatusEnum.CANCEL.getConent();
                break;
        }

        return statusDesc;
    }
}
